Chapter 34
Creating Custom Wizards

by Rob McGregor

In This Chapter

  Property Sheets and Property Pages 1200
  Creating a Wizard 1203
  Sample Program: Off to See the Wizard 1205

Modern Windows applications are chock full of gadgets and controls, including wizards. In an application context, as you know, a wizard walks the user through a series of simple steps to accomplish a complex task. This chapter explains the process of creating a basic wizard and the relationship of wizards to property sheets and property pages.

Property Sheets and Property Pages

Property sheets, also known as tabbed dialog boxes, are commonly used in modern Windows applications. A property sheet is a special type of dialog box usually used to modify the properties of some external object. A property sheet has three main parts:

  The main dialog box window containing OK, Cancel, and Apply buttons in the lower-right corner
  One or more property page dialog resources, shown one at a time within the main dialog box
  A tab at the top of each property page that the user can click to select that page

Property sheets are generally used when you have a number of related settings or options to change; they allow a large amount of information from multiple dialog boxes to be grouped within a single dialog box. For example, Windows uses a property sheet with various property pages to enable a user to change screen savers, wallpaper, video display modes, user interface attributes, colors, and so on.

MFC provides property sheets and property pages in the CPropertySheet and CPropertyPage classes. Let’s take a quick look at these classes.

The CPropertySheet and CPropertyPage Classes

CPropertySheet objects represent property sheets, and even though CPropertySheet is derived directly from CWnd (and not from CDialog), managing a CPropertySheet object is similar to managing a CDialog object. The methods provided by the CPropertySheet class are listed in Table 34.1.

Table 34.1 CPropertySheet Class Methods

Method Description

AddPage() Adds a page to the property sheet
Construct() Constructs a CPropertySheet object
Create() Displays a modeless property sheet
DoModal() Displays a modal property sheet
EndDialog() Terminates the property sheet
GetActiveIndex() Gets the index of the active page of the property sheet
GetActivePage() Returns the active page object
GetPage() Gets a pointer to the specified page
GetPageCount() Gets the number of pages in the property sheet
GetPageIndex() Gets the index of the specified page of the property sheet
GetTabControl() Gets a pointer to a tab control
PressButton() Simulates the choice of the specified button in a property sheet
RemovePage() Removes a page from the property sheet
SetActivePage() Programmatically sets the active page object
SetFinishText() Sets the text for the Finish button
SetTitle() Sets the caption of the property sheet
SetWizardButtons() Enables wizard buttons for a property sheet
SetWizardMode() Enables wizard mode for a property sheet



Unlike CPropertySheet, the CPropertyPage class is derived from CDialog. CPropertyPage objects represent the individual pages within a property sheet. Just as you do for standard dialog boxes under MFC, you must derive a class from CPropertyPage for each property page in a property sheet. The methods provided by the CPropertyPage class are listed in Table 34.2.

Table 34.2 CPropertyPage Class Methods

Method Description

CancelToClose() Changes the OK button to read Close and disables the Cancel button after an unrecoverable change in the page of a modal property sheet.
OnApply() Called by MFC when the Apply Now button is selected.
OnCancel() Called by MFC when the Cancel button is selected.
OnKillActive() Called by MFC when the current page is no longer the active page. Data validation should be performed here.
OnOK() Called by MFC when the OK, Apply Now, or Close button is selected.
OnQueryCancel() Called by MFC when the Cancel button is selected and before the cancel operation has taken place.
OnReset() Called by MFC when the Cancel button is selected.
OnSetActive() Called by MFC when the page is made the active page.
OnWizardBack() Called by MFC when the Back button is selected while using a wizard-type property sheet.
OnWizardFinish() Called by MFC when the Finish button is selected while using a wizard-type property sheet.
OnWizardNext() Called by MFC when the Next button is selected while using a wizard-type property sheet.
QuerySiblings() Forwards the message to each page of the property sheet.
SetModified() Called to activate or deactivate the Apply Now button.

If this chapter is about Windows wizards, why all this chatter about property sheets and property pages? Did you notice the wizard-specific items in Tables 34.1 and 34.2? Let’s try to make some sense of this. Read on!

The Wizard Walk and the Property Sheet Connection

A wizard is really just a property sheet dialog box with a series of application-defined modeless property page dialog boxes attached. Unlike standard property sheets, wizard-style property sheets don’t have the familiar tabs. Instead, they use buttons to enable the user to move from one step to the next. These buttons are labeled Back and Next, although a Finish button replaces the Next button when the user has completed all the steps. There is also a Cancel button that the user can choose at any time during the wizard walk.


Note:  

Although I don’t go into a great deal of class-specific detail about the CPropertySheet and CPropertyPage classes here, I will use these classes when creating the sample wizard for this chapter.


Wizards are best at breaking complex operations down into concise, easy steps. When a user has completed walking through these steps, the wizard typically performs the complex action for him.

Creating a Wizard

Creating a wizard with MFC isn’t any more difficult than creating a property sheet. There are six general steps to follow when creating a wizard:

1.  Create dialog template resources for each wizard page.
2.  Create a dialog class for each dialog resource.
3.  Allocate the property sheet and its property pages.
4.  Add the pages to the property sheet.
5.  Make the property sheet a wizard.
6.  Add message handlers where appropriate.

Each of these steps is discussed in detail later in this chapter, using the sample application WIZARD1 as an example.


Caution:  

Because each of the wizard’s pages is really just a property page, each must have the thin border style and the child window style. Dialog templates that use the extended styles are not supported by the property sheet API; if you attempt to use them, they will crash. I know—I’ve done it. It’s a difficult bug to track down too, so be careful.


Setting the Wizard Mode

To make a property page think it’s really a powerful wizard, you simply call the CPropertySheet::SetWizardMode() method to set the PSF_WIZARD flag. This method does, in fact, make a property page into a wizard, complete with Back, Next, and Cancel buttons all created automatically. The user walks through the wizard with these buttons, as opposed to using the standard property page tabs.

Enabling the Wizard Buttons

Although the standard Back, Next, and Cancel buttons appear by default, you can choose which buttons you want to be available at any given time by calling the SetWizardButtons() method. This method uses the following prototype:

void SetWizardButtons(DWORD dwFlags);

In this syntax, dwFlags is a set of bit flags used to set the wizard buttons. These flags are combined using the bitwise OR (|) operator and can be a combination of any of the values listed in Table 34.3.

Table 34.3 The Bit Flag Values Used for Setting Wizard Buttons

Bit Flag Meaning

PSWIZB_BACK Displays the Back button
PSWIZB_NEXT Displays the Next button
PSWIZB_FINISH Displays the Finish button
PSWIZB_DISABLEDFINISH Displays Finish button disabled

The Finish and Next buttons are really one and the same, so both can be present at the same time. The caption used for the button changes depending on the flags used in SetWizardButtons(). You call this method only after the wizard is open for business—the CPropertyPage::OnSetActive() method is the perfect place to do this.



Displaying the Wizard

Before you can do anything in the wizard, you have to display it. A C++ property page object is created with a call to the constructor, but the actual Windows dialog box window is created by calling CWnd::DoModal(). This method creates a modal wizard. When used to create a wizard, the DoModal() method returns the value ID_WIZFINISH if the user finished the wizard successfully; it returns the value IDCANCEL if the user canceled the wizard.

Wizard Notification Messages

A wizard control sends notification messages for a page through the message map whenever the page gains or loses the input focus and when a user chooses any of the buttons. These notifications use the standard WM_NOTIFY messaging described in Chapter 3, “The Windows Common Controls.” A wizard page gets the same messages as a standard property page, plus three extra because of its exalted wizard page status. These extra notifications are PSN_WIZBACK, PSN_WIZNEXT, and PSN_WIZFINISH (corresponding to the user choosing the Back, Next, or Finish buttons). The wizard property sheet is destroyed when a user chooses the Finish or Cancel button.

Moving Back

The current dialog page receives a PSN_WIZBACK notification when a user clicks the Back button. The OnWizardBack() notification handler is predefined by MFC to deal with this notification. By adding this method to your dialog class message map, your application can respond to the PSN_WIZBACK message.

Moving Next

The current dialog page receives a PSN_WIZNEXT notification when a user clicks the Next button. The OnWizardNext() notification handler is predefined by MFC to deal with this notification. By adding this method to your dialog class message map, your application can respond to the PSN_WIZNEXT message.

The Big Finish

The Finish button should be displayed or active only when the user has completed all the steps required by the wizard. When a user completes the wizard by choosing the Finish button, the dialog box receives a PSN_WIZFINISH notification. The OnWizardFinished() handler is predefined by MFC to deal with this notification. By adding this method to your dialog class message map, your application can respond to the PSN_WIZFINISH message. When you display the Finish button, you can also call the SetFinishText() method to set the text that appears on the button and to hide the Back and Next buttons.


Note:  

When a user chooses the Back or Next button, the wizard automatically moves to the previous or next page. To prevent this from occurring, return the value -1 from any of the three methods just described: OnWizardBack(), OnWizardNext(), or OnWizardFinished(). To cause a nonsequential jump to any page in the wizard, simply specify the desired page index.


Sample Program: Off to See the Wizard (WIZARD1.EXE)

Now turn your attention to the sample program WIZARD1.EXE. This application displays a sample wizard that does nothing useful at all except demonstrate how a wizard can be created using MFC. At its heart, the application consists of the typical simple application model you’ve used so often before. In this program, however, your old friends the application object and the frame window object team up with a new object: the CWizard object. The CWizard class is derived from CPropertySheet and is owned by the frame window class. In this program, the frame window also owns the five property page classes that are used by the CWizard class.


Note:  

WIZARD1.EXE, along with all its source files, can be found in the CHAP34\SOURCE\WIZARD1 folder on this book’s companion CD-ROM.


The following sections look at each of the steps needed to get the wizard off the ground.

Creating Wizard Page Dialog Template Resources

The first thing to do is create the wizard’s dialog template resources that the five property pages use. Using a dialog editor, you’ll create the five dialog pages required for this program. These five dialog pages are described in the next sections.

The Welcome Page

The Welcome page greets the user and tells him or her what to expect and what to do first. Figure 34.1 shows the IDD_INTRO dialog template under construction in the Visual C++ Developer Studio. The image on the left side of the dialog template is one of four bitmap resources used in this program.


Figure 34.1  IDD_INTRO: The Welcome dialog box at design time.

The About You Page

The second page in the sample wizard is IDD_PAGE1, the About You page. This page is used to gather the user’s first and last names, company name, and email address. Figure 34.2 shows the IDD_PAGE1 dialog template under construction.


Figure 34.2  IDD_PAGE1: The About You dialog box at design time.

The Food for Thought and Compiler Preferences Pages

The next page used by the sample wizard is IDD_PAGE2. Because this page asks a question about food preferences, it’s referred to as the Food for Thought page. Figure 34.3 shows the IDD_PAGE2 dialog template under construction.

Because the IDD_PAGE3 dialog template is used to ask a question about compilers, it’s referred to as the Compiler Preferences page. Figure 34.4 shows IDD_PAGE3 under construction.


Figure 34.3  IDD_PAGE2: The Food for Thought dialog box at design time.


Figure 34.4  IDD_PAGE3: The Compiler Preferences dialog box at design time.

The Final Page

The final page used by the sample wizard is IDD_PAGE4. This page simply tells the user the wizard walk is over and displays the Finish button. Figure 34.5 shows the IDD_PAGE4 dialog template under construction.


Figure 34.5  The final wizard page.

Create a Dialog Class for Each Dialog Resource

The next destination along the yellow brick road (yes, you really will see the wizard at the end of this journey) is the creation of MFC classes to wrap all those dialog templates. One class per template is needed. The classes are split into separate modules to keep things tidier; these modules are listed in Table 34.4.

Table 34.4 The MFC Classes Used to Wrap the Five Wizard Dialog Resources

Class Dialog Resource

CIntroPage IDD_INTRO
CPage1 IDD_PAGE1
CPage2 IDD_PAGE2
CPage3 IDD_PAGE3
CPage4 IDD_PAGE4

These five classes have the same general form. For the purposes of this sample wizard, I’ve kept things fairly minimal. The text on all the pages is read from a string table in the resource file, and the bitmaps displayed on each page are stored as resource data as well. To get a general idea how all these classes operate, the following sections look more closely at each.



Exploring the Welcome Page: Class CIntroPage

The CIntroPage class is very simple because nothing much happens on the Welcome page. The most interesting things about the class are the dialog box initialization in CIntroPage::OnInitDialog(), and the dialog box painting in CIntroPage::OnPaint(). The interface for the CIntroPage class is shown in Listing 34.1.

Listing 34.1 The Interface for the CIntroPage Class (INTROPG.H)


///////////////////////////////////////////////////////////////////
// CIntroPage dialog

class CIntroPage : public CPropertyPage
{
   DECLARE_DYNCREATE(CIntroPage)

// Construction
public:
   CIntroPage();
   ~CIntroPage();

   // Dialog Data
   enum { IDD = IDD_INTRO };

protected:
   CFont m_fntTitle;  // font for the title of this page

   // DDX/DDV support
   virtual void DoDataExchange(CDataExchange* pDX);

   // Message map entries
   afx_msg void OnPaint();
   virtual BOOL OnInitDialog();

   DECLARE_MESSAGE_MAP()
};

Initializing the Welcome Page

Like any other MFC dialog box, the Welcome page uses the OnInitDialog() method to perform any dialog box-specific initialization of member variables and controls. The overridden method CIntroPage::OnInitDialog() is shown in Listing 34.2.

Listing 34.2 The CIntroPage::OnInitDialog() Method


//////////////////////////////////////////////////////////////////////////
// CIntroPage::OnInitDialog()

BOOL CIntroPage::OnInitDialog()
{
   CPropertyPage::OnInitDialog();

   m_fntTitle.CreateFont(TITLE_SIZE, 0, 0, 0, FW_BOLD, FALSE, FALSE,
      0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
      DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN, “Times New Roman”);

   return TRUE;
}

This method calls the inherited method first and then uses the CFont::CreateFont() method to create a custom Times New Roman font of size TITLE_SIZE (defined, in this case, as 25). The new font is stored in the protected data member m_fntTitle to be used throughout the life of the dialog box.


Note:  

All the other pages in the WIZARD1 program use the same type of initialization for a custom font. Each page stores its own custom font for later use in the protected member m_fntTitle.


Using the New Font and Displaying Text Messages

The title for the Welcome page is displayed using the custom font created in OnInitDialog(). Two static controls are supplied with text messages read from string table resources defined in WIZARD1.RC. The CIntroPage::OnPaint() method shows how all this is done (see Listing 34.3).

Listing 34.3 The CIntroPage::OnPaint() Method


//////////////////////////////////////////////////////////////////////////
// CIntroPage::OnPaint()

void CIntroPage::OnPaint()
{
   CPaintDC dc(this); // device context for painting

   // Change the font
   CStatic* pTitle = (CStatic*)GetDlgItem(IDC_TITLE);
   ASSERT_VALID(pTitle);
   pTitle->SetFont(&m_fntTitle);

   // Draw the text
   CString str;
   str.LoadString(IDS_WELCOME);
   SetDlgItemText(IDC_TITLE, (LPCTSTR)str);

   str.LoadString(IDS_INTRO1);
   SetDlgItemText(IDC_INTRO1, (LPCTSTR)str);

   str.LoadString(IDS_INTRO2);
   SetDlgItemText(IDC_INTRO2, (LPCTSTR)str);
}

To change the font for the Welcome title, this method first uses CWnd::GetDlgItem() to get a pointer to the control with the identifier IDC_TITLE. This pointer is cast as a CStatic* to the local variable pTitle. Then the pointer is used for a call to the CWnd::SetFont() method to set the custom m_fntTitle font as the default for the IDC_TITLE static control.

The messages displayed in the other static controls are read out of the application’s resources using the CString::LoadString() method with the appropriate identifier. The result of all this manipulation can be seen in Figure 34.6.


Figure 34.6  The WIZARD1 wizard’s Welcome page at runtime.

Exploring the About You Page: Class CPage1

The CPage1 class adds to the minimal functionality provided by CIntroPage. This dialog box expects you to fill in data in four edit controls, although only two are mandatory. The dialog box initialization and painting routines are much the same as for CIntroPage. The real fun comes in handling the edit controls. You use DDX and DDV as well as a custom data structure to store user selections. The interface for the CPage1 class is shown in Listing 34.4.

Listing 34.4 The Interface for the CPage1 Class (PAGE1.H)


//////////////////////////////////////////////////////////////////////////
// CPage1 dialog

class CPage1 : public CPropertyPage
{
   DECLARE_DYNCREATE(CPage1)

public:
   // Construction
   CPage1();
   ~CPage1();

   // Dialog Data
   enum { IDD = IDD_PAGE1 };

   CString   m_sCompany;
   CString   m_sEmail;
   CString   m_sFirstName;
   CString   m_sLastName;

public:
   // Overrides
   virtual LRESULT OnWizardNext();

protected:
   // DDX/DDV support
    virtual void DoDataExchange(CDataExchange* pDX);

protected:
   CFont m_fntTitle;

   // Control access methods
   inline CEdit& editFirst()
      { return *(CEdit*) GetDlgItem(IDC_FIRSTNAME); }

   inline CEdit& editLast()
      { return *(CEdit*) GetDlgItem(IDC_LASTNAME); }

protected:
   // Message map members
   afx_msg void OnPaint();
   virtual BOOL OnInitDialog();

   DECLARE_MESSAGE_MAP()
};

Note the usage of the GetDlgItem()function within the editFirst() control access method. This method returns a reference to a control and makes using the dialog controls very convenient; consider this example:

inline CEdit& editFirst()
   { return *(CEdit*) GetDlgItem(IDC_FIRSTNAME); }

DDX and DDV for CPage1

Like any other MFC dialog box, the About You page uses the CPage1::DoDataExchange() method to perform data exchange and validation on the edit controls and data members. The override method CPage1::DoDataExchange() is shown in Listing 34.5.

Listing 34.5 The CPage1::DoDataExchange() Method


///////////////////////////////////////////////////////////////////
//  CPage1::DoDataExchange()

void CPage1::DoDataExchange(CDataExchange* pDX)
{
   // Call the inherited method
   CPropertyPage::DoDataExchange(pDX);

   // Date map
   DDX_Text(pDX, IDC_COMPANY, m_sCompany);
   DDX_Text(pDX, IDC_EMAIL, m_sEmail);
   DDX_Text(pDX, IDC_FIRSTNAME, m_sFirstName);
   DDX_Text(pDX, IDC_LASTNAME, m_sLastName);
}



Ensuring that Required Data Is Entered

Of the four edit controls on the CPage1 dialog box, only the first two (IDC_FIRSTNAME and IDC_LASTNAME) are required to have text in them. This requirement is enforced in the CPage1::OnWizardNext() method, which is called by MFC when a user clicks the Next button. If these two edit controls are empty, the wizard displays a dialog box explaining the problem and sets the focus on the control in question. The CPage1 dialog box is shown in Figure 34.7 as it appears at runtime; the CPage1::OnWizardNext() method shows how all this is done (see Listing 34.6).


Figure 34.7  The WIZARD1 wizard’s About You page at runtime.

Listing 34.6 The CPage1::OnWizardNext() Method


///////////////////////////////////////////////////////////////////
// CPage1::OnWizardNext()

LRESULT CPage1::OnWizardNext()
{
   // Updata and verify dialog data
   UpdateData(TRUE);

   // Check to see that text exists in Name edit controls
   if (m_sFirstName.GetLength() == 0)
   {
      MessageBeep(MB_ICONASTERISK);
      AfxMessageBox(“You must enter your First Name...”,
         MB_OK | MB_ICONINFORMATION);
      editFirst().SetFocus();

      // Prevent the page from turning
      return -1;

   }

   if (m_sLastName.GetLength() == 0)
   {
      MessageBeep(MB_ICONASTERISK);
      AfxMessageBox(“You must enter your Last Name...”,
         MB_OK | MB_ICONINFORMATION);
      editLast().SetFocus();

      // Prevent the page from turning
      return -1;

   }


   // Get the parent window
   CWizard* pWiz = (CWizard*) GetParent();
   ASSERT_VALID(pWiz);

   // Update the wizard data
   pWiz->m_swd.sFirstName = m_sFirstName;
   pWiz->m_swd.sLastName  = m_sLastName;
   pWiz->m_swd.sCompany   = m_sCompany;
   pWiz->m_swd.sEmail     = m_sEmail;

   // Call the inherited method
   return CPropertyPage::OnWizardNext();

}
}

By calling UpdateData(TRUE) and checking the lengths of the class member variable strings, you can determine whether the edit control is empty. If the string length is zero, a message box informs the user, as in this code snippet:

if (m_sLastName.GetLength() == 0)
{
   MessageBeep(MB_ICONASTERISK);
   AfxMessageBox(“You must enter your Last Name...”,
      MB_OK | MB_ICONINFORMATION);
   editLast().SetFocus();

   // Prevent the page from turning
   return -1;

}

Note that by returning -1 as the LRESULT, you can prevent the page from turning. If the edit controls contain text, you get the parent wizard window (the property sheet) by using this code:

CWizard* pWiz = (CWizard*) GetParent();

And then you update the SAMPLEWIZDATA structure, like this:

// Update the wizard data
pWiz->m_swd.sFirstName = m_sFirstName;
pWiz->m_swd.sLastName  = m_sLastName;
pWiz->m_swd.sCompany   = m_sCompany;
pWiz->m_swd.sEmail     = m_sEmail;

Finally, you use the return value from the inherited method.

The SAMPLEWIZDATA Structure

The SAMPLEWIZDATA structure is an application-defined structure defined in the header file WIZDATA.H. The SAMPLEWIZDATA structure looks like this:

typedef struct tagSAMPLEWIZDATA
{
   CString  sFirstName;
   CString  sLastName;
   CString  sCompany;
   CString  sEmail;
   CString  sFood;
   CString  sCompiler;

}
SAMPLEWIZDATA;

This structure is used by the CWizard class to collect all the user’s selections throughout the wizard walk.

Updating Wizard Information for CPage2 and CPage3

The CPage2 and CPage3 classes each use a similar override of the OnWizardNext() method to update the data from radio button selections. Listing 34.7 shows how they do it. The CPage2 dialog box is shown in Figure 34.8; the CPage3 dialog box is shown in Figure 34.9.


Figure 34.8  The WIZARD1 wizard’s Food for Thought page at runtime.


Figure 34.9  The WIZARD1 wizard’s Compiler Decisions page at runtime.

Listing 34.7 The CPage2::OnWizardNext() Method


//////////////////////////////////////////////////////////////////////////
// CPage2::OnWizardNext()

LRESULT CPage2::OnWizardNext()
{
   // Get the parent window
   CWizard* pWiz = (CWizard*) GetParent();
   ASSERT_VALID(pWiz);

   // Update the wizard data
   CString str;

   if (btn1().GetCheck() == 1)
      str.LoadString(IDS_PAGE2_1);

   else if (btn2().GetCheck() == 1)
      str.LoadString(IDS_PAGE2_2);

   else if (btn3().GetCheck() == 1)
      str.LoadString(IDS_PAGE2_3);

   else if (btn4().GetCheck() == 1)
      str.LoadString(IDS_PAGE2_4);

   pWiz->m_swd.sFood = str;

   return CPropertyPage::OnWizardNext();
}

The Big Finish

A property page’s OnSetActive() method is called by MFC when it becomes the active page. To display the Finish button on the last page of the wizard, the CPage4 dialog box calls the method CPropertyPage::SetWizardButtons() with the appropriate flags (see Listing 34.8). The end result is shown in Figure 34.10.


Figure 34.10  The Finished! page.

Listing 34.8 The CPage4::OnSetActive() Method


BOOL CPage4::OnSetActive()
{
   // Display the Finish button
   CPropertySheet* pParent = (CPropertySheet*)GetParent();
   pParent->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH);

   // Call the inherited method
   return CPropertyPage::OnSetActive();
}

Figure 34.11 shows the results of data gathered by the wizard displayed in a simple message box.


Figure 34.11  Reviewing the results of data gathered by the wizard.

Creating and Displaying the Wizard

The work of creating and displaying the wizard is done in the CMainWnd::OnFileRunWizard() method. The CMainWnd data member m_pwndWizard is allocated with a call to the CWizard constructor CWizard::CWizard(). This constructor mirrors that of the CPropertySheet class because CWizard is directly derived from CPropertySheet. Each of the property pages follows suit, allocated using the new operator and initialized with the CPropertySheet::Construct() method. The CMainWnd::OnFileRunWizard() method is shown in Listing 34.9.

Listing 34.9 The CMainWnd::OnFileRunWizard() Method


///////////////////////////////////////////////////////////////////
// CMainWnd::OnFileRunWizard()

void CMainWnd::OnFileRunWizard()
{
   // Create the wizard
   m_pwndWizard = new CWizard(“Sample Wizard”, this);
   ASSERT_VALID(m_pwndWizard);

   // Construct the property pages
   m_pdlgIntro = new CIntroPage;
   ASSERT_VALID(m_pdlgIntro);
   m_pdlgIntro->Construct(IDD_INTRO, 0);

   m_pdlgPage1 = new CPage1;
   ASSERT_VALID(m_pdlgPage1);
   m_pdlgPage1->Construct(IDD_PAGE1, 0);

   m_pdlgPage2 = new CPage2;
   ASSERT_VALID(m_pdlgPage2);
   m_pdlgPage2->Construct(IDD_PAGE2, 0);

   m_pdlgPage3 = new CPage3;
   ASSERT_VALID(m_pdlgPage3);
   m_pdlgPage3->Construct(IDD_PAGE3, 0);

   m_pdlgPage4 = new CPage4;
   ASSERT_VALID(m_pdlgPage4);
   m_pdlgPage4->Construct(IDD_PAGE4, 0);

   // Add the property pages to the property sheet
   m_pwndWizard->AddPage(m_pdlgIntro);
   m_pwndWizard->AddPage(m_pdlgPage1);
   m_pwndWizard->AddPage(m_pdlgPage2);
   m_pwndWizard->AddPage(m_pdlgPage3);
   m_pwndWizard->AddPage(m_pdlgPage4);

   // Make the property sheet a Wizard
   m_pwndWizard->SetWizardMode();

   // Display the Wizard
   m_pwndWizard->DoModal();
}

The pages are added to the wizard by calling CPropertySheet::AddPage() and the property sheet is transformed into a wizard with a call to CPropertySheet::SetWizardMode(). To display the wizard, the CWnd::DoModal() method is called.

Summary

A wizard is simply a property sheet and some property pages. The property sheet has the style PSH_WIZARD. This chapter describes what a wizard is and goes step by step through the creation of a simple wizard. Although the sample wizard serves no useful purpose other than as a learning tool, it does reveal the secrets to performing true Windows wizardry!